/*
 * This file is part of aion-unique <aion-unique.org>.
 *
 * aion-unique is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * aion-unique is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with aion-unique.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.aionemu.gameserver.questEngine;

import gnu.trove.TIntArrayList;
import gnu.trove.TIntObjectHashMap;

import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;

import javolution.util.FastMap;

import org.apache.log4j.Logger;

import com.aionemu.commons.utils.Rnd;
import com.aionemu.gameserver.configs.main.GroupConfig;
import com.aionemu.gameserver.dataholders.DataManager;
import com.aionemu.gameserver.dataholders.QuestsData;
import com.aionemu.gameserver.model.PlayerClass;
import com.aionemu.gameserver.model.TaskId;
import com.aionemu.gameserver.model.drop.DropItem;
import com.aionemu.gameserver.model.drop.DropTemplate;
import com.aionemu.gameserver.model.gameobjects.Creature;
import com.aionemu.gameserver.model.gameobjects.Item;
import com.aionemu.gameserver.model.gameobjects.VisibleObject;
import com.aionemu.gameserver.model.gameobjects.player.Player;
import com.aionemu.gameserver.model.gameobjects.player.PlayerCommonData;
import com.aionemu.gameserver.model.gameobjects.player.SkillListEntry;
import com.aionemu.gameserver.model.templates.QuestTemplate;
import com.aionemu.gameserver.model.templates.quest.CollectItem;
import com.aionemu.gameserver.model.templates.quest.CollectItems;
import com.aionemu.gameserver.model.templates.quest.ExtraRewards;
import com.aionemu.gameserver.model.templates.quest.FinishedQuestConds;
import com.aionemu.gameserver.model.templates.quest.NpcQuestData;
import com.aionemu.gameserver.model.templates.quest.QuestDrop;
import com.aionemu.gameserver.model.templates.quest.QuestItems;
import com.aionemu.gameserver.model.templates.quest.QuestWorkItems;
import com.aionemu.gameserver.model.templates.quest.Rewards;
import com.aionemu.gameserver.model.templates.spawn.SpawnTemplate;
import com.aionemu.gameserver.network.aion.serverpackets.SM_CUBE_UPDATE;
import com.aionemu.gameserver.network.aion.serverpackets.SM_QUEST_ACTION;
import com.aionemu.gameserver.questEngine.handlers.QuestHandler;
import com.aionemu.gameserver.questEngine.handlers.models.QuestScriptData;
import com.aionemu.gameserver.questEngine.model.QuestEnv;
import com.aionemu.gameserver.questEngine.model.QuestState;
import com.aionemu.gameserver.questEngine.model.QuestStatus;
import com.aionemu.gameserver.services.CubeExpandService;
import com.aionemu.gameserver.services.WarehouseService;
import com.aionemu.gameserver.spawnengine.SpawnEngine;
import com.aionemu.gameserver.utils.MathUtil;
import com.aionemu.gameserver.utils.PacketSendUtility;
import com.aionemu.gameserver.utils.ThreadPoolManager;
import com.aionemu.gameserver.world.zone.ZoneName;

/**
 * @author MrPoke
 * 
 */
public class QuestEngine
{
	private static final Logger		log			= Logger.getLogger(QuestEngine.class);

	private static final QuestsData	questData	= DataManager.QUEST_DATA;

	public static boolean questFinish(QuestEnv env)
	{
		return questFinish(env, 0);
	}

	public static boolean questFinish(QuestEnv env, int reward)
	{
		Player player = env.getPlayer();
		int id = env.getQuestId();
		QuestState qs = player.getQuestStateList().getQuestState(id);
		if (qs == null || qs.getStatus() != QuestStatus.REWARD)
			return false;
		QuestTemplate template = questData.getQuestById(id);
		List<QuestItems> questItems = new ArrayList<QuestItems>();
		Rewards rewards = template.getRewards().get(reward);
		questItems.addAll(rewards.getRewardItem());

		if (template.getExtraRewards() != null)
		{
			ExtraRewards er = template.getExtraRewards();
			if (qs.getCompleteCount() == er.getRepeatCount()-1)
			{
				questItems.addAll(er.getRewardItem());
			}
		}
		int dialogId = env.getDialogId();
		if (dialogId != 17 && dialogId != 0)
		{
			if (template.isUseClassReward())
			{
				QuestItems classRewardItem = null;
				PlayerClass playerClass = player.getObjectTemplate().getPlayerClass();
				switch (playerClass)
				{
					case ASSASSIN:
						if (template.getAssassinSelectableReward() != null)
							classRewardItem = template.getAssassinSelectableReward().get(dialogId - 8);
						break;
					case CHANTER:
						if (template.getChanterSelectableReward() != null)
							classRewardItem = template.getChanterSelectableReward().get(dialogId - 8);
						break;
					case CLERIC:
						if (template.getPriestSelectableReward() != null)
							classRewardItem = template.getPriestSelectableReward().get(dialogId - 8);
						break;
					case GLADIATOR:
						if (template.getFighterSelectableReward() != null)
							classRewardItem = template.getFighterSelectableReward().get(dialogId - 8);
						break;
					case RANGER:
						if (template.getRangerSelectableReward() != null)
							classRewardItem = template.getRangerSelectableReward().get(dialogId - 8);
						break;
					case SORCERER:
						if (template.getWizardSelectableReward() != null)
							classRewardItem = template.getWizardSelectableReward().get(dialogId - 8);
						break;
					case SPIRIT_MASTER:
						if (template.getElementalistSelectableReward() != null)
							classRewardItem = template.getElementalistSelectableReward().get(dialogId - 8);
						break;
					case TEMPLAR:
						if (template.getKnightSelectableReward() != null)
							classRewardItem = template.getKnightSelectableReward().get(dialogId - 8);
						break;
				}
				if (classRewardItem != null)
					questItems.add(classRewardItem);
			}
			else
			{
				QuestItems selectebleRewardItem = rewards.getSelectableRewardItem().get(dialogId - 8);
				if (selectebleRewardItem != null)
					questItems.add(selectebleRewardItem);
				if (template.getExtraRewards() != null)
				{
					ExtraRewards er = template.getExtraRewards();
					if (qs.getCompleteCount() == er.getRepeatCount()-1)
					{
						QuestItems extraSelectebleRewardItem = rewards.getSelectableRewardItem().get(dialogId - 8);
						if (extraSelectebleRewardItem != null)
							questItems.add(extraSelectebleRewardItem);
					}
				}
			}
		}
		if (Item.addItems(player, questItems))
		{
			if (rewards.getGold() != null)
			{
				Item.increaseKinah(player, player.getRates().getQuestKinahRate() * rewards.getGold());
			}
			if (rewards.getExp() != null)
			{
				int rewardExp = (player.getRates().getQuestXpRate() * rewards.getExp());
				player.getObjectTemplate().addExp(rewardExp);
			}

			if (rewards.getTitle() != null)
			{
				player.getTitleList().addTitle(rewards.getTitle());
			}

			if (rewards.getRewardAbyssPoint() != null)
			{
				player.getObjectTemplate().addAp(rewards.getRewardAbyssPoint());
			}

			if (template.getExtraRewards() != null)
			{
				ExtraRewards er = template.getExtraRewards();
				if (qs.getCompleteCount() == er.getRepeatCount()-1)
				{
					if (er.getGold() != null)
					{
						Item.increaseKinah(player, player.getRates().getQuestKinahRate() * er.getGold());
					}
					if (er.getExp() != null)
					{
						int rewardExp = (player.getRates().getQuestXpRate() * er.getExp());
						player.getObjectTemplate().addExp(rewardExp);
					}

					if (er.getTitle() != null)
					{
						player.getTitleList().addTitle(er.getTitle());
					}
				}
			}

			if (rewards.getExtendInventory() != null)
			{
				if (rewards.getExtendInventory() == 1)
					CubeExpandService.expand(player);
				else if (rewards.getExtendInventory() == 2)
					WarehouseService.expand(player);
			}

			if (rewards.getExtendStigma() != null)
			{
				PlayerCommonData pcd = player.getObjectTemplate();
				pcd.setAdvencedStigmaSlotSize(pcd.getAdvencedStigmaSlotSize() + 1);
				PacketSendUtility.sendPacket(player, new SM_CUBE_UPDATE(player, 6, pcd.getAdvencedStigmaSlotSize()));
			}
			//remove all worker list item if finished.
			QuestWorkItems qwi = questData.getQuestById(id).getQuestWorkItems();

			if (qwi != null)
			{
				long count = 0;
				for (QuestItems qi : qwi.getQuestWorkItem())
				{
					if (qi != null)
					{
						Item.decreaseItemCountByItemId(player, qi.getItemId(), count);
					}
				}
			}

			qs.setStatus(QuestStatus.COMPLETE);
			qs.setCompleteCount(qs.getCompleteCount() + 1);
			PacketSendUtility.sendPacket(player, new SM_QUEST_ACTION(id, qs.getStatus(), qs.getQuestVars().getQuestVars()));
			player.updateNearbyQuests();
			QuestEngine.getInstance().onLvlUp(env);
			return true;
		}
		return true;
	}

	public static boolean checkStartCondition(QuestEnv env)
	{

		Player player = env.getPlayer();
		QuestTemplate template = questData.getQuestById(env.getQuestId());
		if (template == null)
		{
			log.warn("Missing quest template: questId= "+env.getQuestId());
			return false;
		}

		if (template.getRacePermitted() != null)
		{
			if (template.getRacePermitted() != player.getObjectTemplate().getRace())
				return false;
		}

		// min level - 2 so that the gray quest arrow shows when quest is almost available 
		// quest level will be checked again in QuestService.startQuest() when attempting to start
		if (player.getLevel() < template.getMinlevelPermitted() - 2)
		{
			return false;
		}

		if (template.getClassPermitted().size() != 0)
		{
			if (!template.getClassPermitted().contains(player.getObjectTemplate().getPlayerClass()))
				return false;
		}

		if (template.getGenderPermitted() != null)
		{
			if (template.getGenderPermitted() != player.getObjectTemplate().getGender())
				return false;
		}
		if (template.getFinishedQuestConds() != null)
		{
			boolean or = false;
			for (FinishedQuestConds questCond : template.getFinishedQuestConds())
			{
				boolean and = true;
				for (int questId : questCond.getFinishedQuestCond())
				{
					QuestState qs = player.getQuestStateList().getQuestState(questId);
					and &= (qs != null && qs.getStatus() == QuestStatus.COMPLETE);
				}
				or |=and;
				if (or)
					break;
			}
			if (!or)
				return false;
		}

		if (template.getCombineSkill() != null)
		{
			if (template.getCombineSkill() == -1)
			{
				boolean found = false;
				int[] skills = {40001, 40002, 40003, 40004, 40007, 40008};
				for (int i : skills)
				{
					SkillListEntry skill = player.getSkillList().getSkillEntry(i);
					if (skill == null)
						continue;
					if (skill.getSkillLevel() < template.getCombineSkillPoint() || skill.getSkillLevel() - 40 > template.getCombineSkillPoint())
						continue;
					found = true;
				}
				if (!found)
					return false;
			}
			else
			{
				SkillListEntry skill = player.getSkillList().getSkillEntry(template.getCombineSkill());
				if (skill == null)
					return false;
				if (skill.getSkillLevel() < template.getCombineSkillPoint() || skill.getSkillLevel() - 40 > template.getCombineSkillPoint())
					return false;
			}
		}

		if (template.getUnfinishedQuestConds() != null)
		{
			for (int id : template.getUnfinishedQuestConds())
			{
				QuestState qs = player.getQuestStateList().getQuestState(id);
				if (qs == null)
					continue;
				if (qs.getStatus() == QuestStatus.COMPLETE)
					return false;
			}
		}
		
		if (template.getNoacquiredQuestCond() != null)
		{
			for (int id: template.getNoacquiredQuestCond())
			{
				QuestState qs = player.getQuestStateList().getQuestState(id);
				if (qs == null || qs.getStatus() == QuestStatus.NONE || qs.getStatus() == QuestStatus.COMPLETE)
					continue;
				return false;
			}
		}
		QuestState qs = player.getQuestStateList().getQuestState(template.getId());
		if (qs != null && qs.getStatus().value() > 0)
		{
			if (qs.getStatus() == QuestStatus.COMPLETE && (qs.getCompleteCount() < template.getMaxRepeatCount()))
			{
				return true;
			}
			else
			{
				return false;
			}
		}
		return true;
	}

	public static boolean startQuest(QuestEnv env, QuestStatus questStatus)
	{
		Player player = env.getPlayer();
		int id = env.getQuestId();
		QuestTemplate template = questData.getQuestById(env.getQuestId());
		if (questStatus != QuestStatus.LOCKED)
		{
			if (!checkStartCondition(env))
				return false;

			if (player.getLevel() < template.getMinlevelPermitted())
			{
				// Should not reach this point. Except for a location started quest.
				return false;
			}
		}
		PacketSendUtility.sendPacket(player, new SM_QUEST_ACTION(id, questStatus.value(), 0));
		QuestState qs = player.getQuestStateList().getQuestState(id);
		if (qs == null)
		{
			qs = new QuestState(template.getId(), questStatus, 0, 0);
			player.getQuestStateList().addQuest(id, qs);
		}
		else
		{
			qs.setStatus(questStatus);
			qs.setQuestVar(0);
		}
		player.updateNearbyQuests();
		return true;
	}

	public boolean questComplite(QuestEnv env)
	{
		Player player = env.getPlayer();
		int id = env.getQuestId();
		QuestState qs = player.getQuestStateList().getQuestState(id);
		if (qs == null || qs.getStatus() != QuestStatus.START)
			return false;

		qs.setQuestVarById(0, qs.getQuestVarById(0) + 1);
		qs.setStatus(QuestStatus.REWARD);
		PacketSendUtility.sendPacket(player, new SM_QUEST_ACTION(id, qs.getStatus(), qs.getQuestVars().getQuestVars()));
		player.updateNearbyQuests();
		return true;
	}

	public static boolean collectItemCheck(QuestEnv env, boolean removeItem)
	{
		Player player = env.getPlayer();
		int id = env.getQuestId();
		QuestState qs = player.getQuestStateList().getQuestState(id);
		if (qs == null)
			return false;
		QuestTemplate template = questData.getQuestById(env.getQuestId());
		CollectItems collectItems = template.getCollectItems();
		if (collectItems == null)
			return true;
		for (CollectItem collectItem : collectItems.getCollectItem())
		{
			long count = player.getInventory().getItemCountByItemId(collectItem.getItemId());
			if (collectItem.getCount() > count)
				return false;
		}
		if (removeItem)
		{
			for (CollectItem collectItem : collectItems.getCollectItem())
			{
				Item.decreaseItemCountByItemId(player, collectItem.getItemId(), collectItem.getCount());
			}
		}
		return true;
	}

	public static VisibleObject addNewSpawn(int worldId, int instanceId, int templateId, float x, float y, float z, byte heading, boolean noRespawn)
	{
		SpawnTemplate spawn = SpawnEngine.getInstance().addNewSpawn(worldId, instanceId, templateId, x, y, z, heading, 0, 0, noRespawn);
		return SpawnEngine.getInstance().spawnObject(spawn, instanceId);
	}

	public static void getQuestDrop(Map<Integer, DropItem> droppedItems, int index, VisibleObject npc, Player player)
	{
		List<QuestDrop> drops = QuestEngine.getInstance().getQuestDrop(npc.getObjectTemplate().getTemplateId());
		if (drops.isEmpty())
			return;
		List<Player> players = new ArrayList<Player>();
		if (player.isInGroup())
		{
			for (Player member : player.getPlayerGroup().getOnlineMembers())
			{
				if (MathUtil.isInRange(member, npc, GroupConfig.GROUP_MAX_DISTANCE))
				{
					players.add(member);
				}
			}
		}
		else
		{
			players.add(player);
		}

		for (QuestDrop drop : drops)
		{
			if (drop.isDropEachMember())
			{
				Iterator<Player> iterator = players.iterator();
				while (iterator.hasNext())
				{
					Player mamber = iterator.next();
					if (!isDrop(mamber, drop))
						iterator.remove();
						
				}
				if (players.isEmpty())
					return;
				Player rewardPlayer = players.get(Rnd.get(players.size()));
				DropItem item = new DropItem(new DropTemplate(drop.getNpcId(), drop.getItemId(), 1, 1, drop.getChance()));
				item.setPlayerObjId(rewardPlayer.getObjectId());
				item.setCount(1);
				droppedItems.put(index++, item);
			}
			else
			{
				for (Player member : players)
				{
					if (isDrop(member, drop))
					{
						DropItem item = new DropItem(new DropTemplate(drop.getNpcId(), drop.getItemId(), 1, 1, drop.getChance()));
						item.setPlayerObjId(member.getObjectId());
						item.setCount(1);
						droppedItems.put(index++, item);
					}
				}
			}
		}
	}

	private static boolean isDrop(Player player, QuestDrop drop)
	{
		if (Rnd.get() * 100 > drop.getChance())
			return false;
		int questId = drop.getQuestId();
		QuestState qs = player.getQuestStateList().getQuestState(questId);
		if (qs == null || qs.getStatus() != QuestStatus.START)
			return false;
		QuestTemplate template = questData.getQuestById(questId);
		CollectItems collectItems = template.getCollectItems();
		if (collectItems == null)
			return true;

		for (CollectItem collectItem : collectItems.getCollectItem())
		{
			int collectItemId = collectItem.getItemId();
			int dropItemId = drop.getItemId();
			if (collectItemId != dropItemId)
				continue;
			long count = player.getInventory().getItemCountByItemId(collectItemId);
			if (collectItem.getCount() > count)
				return true;
		}
		return false;
	}

	/**
	* @param id
	* @param playerLevel
	* @return false if player is 2 or more levels below quest level 
	*/
	public static boolean checkLevelRequirement(int questId, int playerLevel)
	{
		QuestTemplate template = questData.getQuestById(questId);
		return (playerLevel >= template.getMinlevelPermitted());
	}

	public static boolean questTimerStart(QuestEnv env, int timeInSeconds)
	{
		final Player player = env.getPlayer();
		final int id = env.getQuestId();

		// Schedule Action When Timer Finishes
		Future<?> task = ThreadPoolManager.getInstance().schedule(new Runnable()
		{

			@Override
			public void run()
			{
				QuestEngine.getInstance().onQuestTimerEnd(new QuestEnv(null, player, 0, 0));
				QuestEngine.getInstance().deleteQuest(player, id);
				PacketSendUtility.sendPacket(player, new SM_QUEST_ACTION(id));
				player.updateNearbyQuests();
			}
		}, timeInSeconds * 1000);
		player.addTask(TaskId.QUEST_TIMER, task);
		PacketSendUtility.sendPacket(player, new SM_QUEST_ACTION(id, timeInSeconds));
		return true;
	}

	public static boolean questTimerEnd(QuestEnv env)
	{
		final Player player = env.getPlayer();
		final int id = env.getQuestId();

		player.cancelTask(TaskId.QUEST_TIMER);
		PacketSendUtility.sendPacket(player, new SM_QUEST_ACTION(id, 0));
		return true;
	}

	private static final FastMap<Integer, QuestHandler>	questHandlers			= new FastMap<Integer, QuestHandler>();

	public static final File							QUEST_DESCRIPTOR_FILE	= new File("./data/scripts/system/quest_handlers.xml");

	private final TIntObjectHashMap<NpcQuestData>		npcQuestData			= new TIntObjectHashMap<NpcQuestData>();
	private final TIntObjectHashMap<TIntArrayList>		questItemIds			= new TIntObjectHashMap<TIntArrayList>();
	private final TIntArrayList							questLvlUp				= new TIntArrayList();
	private final FastMap<ZoneName, TIntArrayList>		questEnterZone			= new FastMap<ZoneName, TIntArrayList>();
	private final TIntObjectHashMap<TIntArrayList>		questMovieEndIds		= new TIntObjectHashMap<TIntArrayList>();
	private final TIntArrayList							questOnDie				= new TIntArrayList();
	private final TIntArrayList							questOnEnterWorld		= new TIntArrayList();
	private final TIntObjectHashMap<List<QuestDrop>>	questDrop				= new TIntObjectHashMap<List<QuestDrop>>();
	private final List<Integer>							questOnQuestTimerEnd	= new ArrayList<Integer>();
	private final TIntArrayList							questOnQuestAbort		= new TIntArrayList();

	private final NpcQuestData							emptyNpcQuestData		= new NpcQuestData();

	public static final QuestEngine getInstance()
	{
		return SingletonHolder.instance;
	}

	// Constructor
	private QuestEngine()
	{
		log.info("Initializing QuestEngine");
	}

	public void load()
	{
		for (QuestTemplate data : questData.getQuestsData())
		{
			if (data.getQuestDrop() == null)
				continue;
			for (QuestDrop drop : data.getQuestDrop())
			{
				drop.setQuestId(data.getId());
				setQuestDrop(drop.getNpcId()).add(drop);
			}
		}

		for (QuestScriptData data : DataManager.QUEST_SCRIPTS_DATA.getData())
		{
			data.register();
		}
		log.info("Loaded " + questHandlers.size() + " quest handler.");
	}

	public void shutdown()
	{
		clear();
		log.info("Quests are shutdown...");
	}

	public boolean onDialog(QuestEnv env)
	{
		QuestHandler questHandler = null;
		if (env.getQuestId() != 0)
		{
			questHandler = getQuestHandlerByQuestId(env.getQuestId());
			if (questHandler != null)
				if (questHandler.onDialogEvent(env))
					return true;
		}
		else
		{
			VisibleObject npc = env.getVisibleObject();
			for (int questId : getNpcQuestData(npc == null ? 0 : npc.getNpcId()).getOnTalkEvent())
			{
				questHandler = getQuestHandlerByQuestId(questId);
				if (questHandler != null)
					if (questHandler.onDialogEvent(env))
						return true;
			}
		}
		return false;
	}

	public boolean onKill(QuestEnv env)
	{
		Creature npc = (Creature) env.getVisibleObject();
		for (int questId : getNpcQuestData(npc.getNpcId()).getOnKillEvent())
		{
			QuestHandler questHandler = getQuestHandlerByQuestId(questId);
			if (questHandler != null)
				if (questHandler.onKillEvent(env))
					return true;
		}
		return false;
	}

	public boolean onAttack(QuestEnv env)
	{
		Creature npc = (Creature) env.getVisibleObject();

		for (int questId : getNpcQuestData(npc.getNpcId()).getOnAttackEvent())
		{
			QuestHandler questHandler = getQuestHandlerByQuestId(questId);
			if (questHandler != null)
				if (questHandler.onAttackEvent(env))
					return true;
		}
		return false;
	}

	public void onLvlUp(QuestEnv env)
	{
		for (int index = 0; index < questLvlUp.size(); index++)
		{
			QuestHandler questHandler = getQuestHandlerByQuestId(questLvlUp.get(index));
			if (questHandler != null)
				questHandler.onLvlUpEvent(env);
		}
	}

	public void onDie(QuestEnv env)
	{
		for (int index = 0; index < questOnDie.size(); index++)
		{
			QuestHandler questHandler = getQuestHandlerByQuestId(questOnDie.get(index));
			if (questHandler != null)
				questHandler.onDieEvent(env);
		}
	}

	public void onEnterWorld(QuestEnv env)
	{
		for (int index = 0; index < questOnEnterWorld.size(); index++)
		{
			QuestHandler questHandler = getQuestHandlerByQuestId(questOnEnterWorld.get(index));
			if (questHandler != null)
				questHandler.onEnterWorldEvent(env);
		}
	}

	public boolean onItemUseEvent(QuestEnv env, Item item)
	{
		TIntArrayList lists = getQuestItemIds(item.getItemTemplate().getTemplateId());
		for (int index = 0; index < lists.size(); index++)
		{
			QuestHandler questHandler = getQuestHandlerByQuestId(lists.get(index));
			if (questHandler != null)
				if (questHandler.onItemUseEvent(env, item))
					return true;
		}
		return false;
	}

	public boolean onEnterZone(QuestEnv env, ZoneName zoneName)
	{
		TIntArrayList lists = getQuestEnterZone(zoneName);
		for (int index = 0; index < lists.size(); index++)
		{
			QuestHandler questHandler = getQuestHandlerByQuestId(lists.get(index));
			if (questHandler != null)
				if (questHandler.onEnterZoneEvent(env, zoneName))
					return true;
		}
		return false;
	}

	public boolean onMovieEnd(QuestEnv env, int movieId)
	{
		TIntArrayList lists = getQuestMovieEndIds(movieId);
		for (int index = 0; index < lists.size(); index++)
		{
			env.setQuestId(lists.get(index));
			QuestHandler questHandler = getQuestHandlerByQuestId(env.getQuestId());
			if (questHandler != null)
				if (questHandler.onMovieEndEvent(env, movieId))
					return true;
		}
		return false;
	}

	public void onQuestAbort(QuestEnv env)
	{
		for (int index = 0; index < questOnQuestAbort.size(); index++)
		{
			QuestHandler questHandler = getQuestHandlerByQuestId(questOnQuestAbort.get(index));
			if (questHandler != null)
				questHandler.onQuestAbortEvent(env);
		}
	}

	public void onQuestTimerEnd(QuestEnv env)
	{
		for (int questId : questOnQuestTimerEnd)
		{
			QuestHandler questHandler = getQuestHandlerByQuestId(questId);
			if (questHandler != null)
				questHandler.onQuestTimerEndEvent(env);
		}
	}

	public boolean deleteQuest(Player player, int questId)
	{
		if (questData.getQuestById(questId).isCannotGiveup())
			return false;

		QuestState qs = player.getQuestStateList().getQuestState(questId);

		if (qs == null)
			return false;

		qs.setStatus(QuestStatus.NONE);

		this.onQuestAbort(new QuestEnv(null, player, questId, 0));
		//remove all worker list item if abandoned
		QuestWorkItems qwi = questData.getQuestById(questId).getQuestWorkItems();

		if (qwi != null)
		{
			long count = 0;
			for (QuestItems qi : qwi.getQuestWorkItem())
			{
				if (qi != null)
				{
					Item.decreaseItemCountByItemId(player, qi.getItemId(), count);
				}
			}
		}

		return true;
	}

	public NpcQuestData getNpcQuestData(int npcTemplateId)
	{
		if (npcQuestData.containsKey(npcTemplateId))
		{
			return npcQuestData.get(npcTemplateId);
		}
		return emptyNpcQuestData;

	}

	public NpcQuestData setNpcQuestData(int npcTemplateId)
	{
		if (!npcQuestData.containsKey(npcTemplateId))
		{
			npcQuestData.put(npcTemplateId, new NpcQuestData());
		}
		return npcQuestData.get(npcTemplateId);
	}

	public TIntArrayList getQuestItemIds(int itemId)
	{
		if (questItemIds.containsKey(itemId))
		{
			return questItemIds.get(itemId);
		}
		return new TIntArrayList();
	}

	public TIntArrayList setQuestItemIds(int itemId)
	{
		if (!questItemIds.containsKey(itemId))
		{
			questItemIds.put(itemId, new TIntArrayList());
		}
		return questItemIds.get(itemId);
	}

	public List<QuestDrop> setQuestDrop(int npcId)
	{
		if (!questDrop.containsKey(npcId))
		{
			questDrop.put(npcId, new ArrayList<QuestDrop>());
		}
		return questDrop.get(npcId);
	}

	public List<QuestDrop> getQuestDrop(int npcId)
	{
		if (questDrop.containsKey(npcId))
		{
			return questDrop.get(npcId);
		}
		return new ArrayList<QuestDrop>();
	}

	public void addQuestLvlUp(int questId)
	{
		if (!questLvlUp.contains(questId))
			questLvlUp.add(questId);
	}

	public void addOnEnterWorld(int questId)
	{
		if (!questOnEnterWorld.contains(questId))
			questOnEnterWorld.add(questId);
	}

	public void addOnDie(int questId)
	{
		if (!questOnDie.contains(questId))
			questOnDie.add(questId);
	}

	public TIntArrayList getQuestEnterZone(ZoneName zoneName)
	{
		if (questEnterZone.containsKey(zoneName))
		{
			return questEnterZone.get(zoneName);
		}
		return new TIntArrayList();
	}

	public TIntArrayList setQuestEnterZone(ZoneName zoneName)
	{
		if (!questEnterZone.containsKey(zoneName))
		{
			questEnterZone.put(zoneName, new TIntArrayList());
		}
		return questEnterZone.get(zoneName);
	}

	public TIntArrayList getQuestMovieEndIds(int moveId)
	{
		if (questMovieEndIds.containsKey(moveId))
		{
			return questMovieEndIds.get(moveId);
		}
		return new TIntArrayList();
	}

	public TIntArrayList setQuestMovieEndIds(int moveId)
	{
		if (!questMovieEndIds.containsKey(moveId))
		{
			questMovieEndIds.put(moveId, new TIntArrayList());
		}
		return questMovieEndIds.get(moveId);
	}

	public void addOnQuestAbort(int questId)
	{
		if (!questOnQuestAbort.contains(questId))
			questOnQuestAbort.add(questId);
	}

	public void addOnQuestTimerEnd(int questId)
	{
		if (!questOnQuestTimerEnd.contains(questId))
			questOnQuestTimerEnd.add(questId);
	}

	public void clear()
	{
		npcQuestData.clear();
		questItemIds.clear();
		questLvlUp.clear();
		questOnEnterWorld.clear();
		questOnDie.clear();
		questEnterZone.clear();
		questMovieEndIds.clear();
		questDrop.clear();
		questOnQuestTimerEnd.clear();
		questHandlers.clear();
	}

	public void addQuestHandler(QuestHandler questHandler)
	{
		if (questHandlers.containsKey(questHandler.getQuestId()))
			log.warn("Duplicate quest: " + questHandler.getQuestId());
		questHandlers.put(questHandler.getQuestId(), questHandler);
	}

	private QuestHandler getQuestHandlerByQuestId(int questId)
	{
		return questHandlers.get(questId);
	}

	@SuppressWarnings("synthetic-access")
	private static class SingletonHolder
	{
		protected static final QuestEngine	instance	= new QuestEngine();
	}
}